# coding:utf-8

import json
import time
import random
from tornado import ioloop
from django.shortcuts import render
from django.http import HttpResponse
from sockjs.tornado import SockJSRouter, SockJSConnection

def index(request):
    return render(request, "cah/index.html")

'''
指令格式：
传送数据统一为json格式，key为指令，value为指令数据
同一条信息可以包含多条指令

指令列表 - 客户端：
['set_uname', name] # 设置自己的用户名
['say', room_id, text] # 说一句话，room0是大厅
['room_add'] # 创建房间
['room_join', id] # 进入房间，大厅是0号房，所以退出房间是为 room_join(0)
['room_player', 1/0] # 成为玩家/旁观者
['room_list'] # 获取房间列表

['game_start'] # 开始游戏
['wcard_send', id] # 打出一张手牌
['wcard_choose', id] # 裁判：选择一张手牌
['score', player] # 得分

指令列表 - 服务端
['user_info', uid] # 用户信息，单体
['room_add', [房间信息]] # 创建房间，大厅广播
['room_leave', uname] # 离开房间
['say', text] # 说一句话，room0是大厅，广播
['room_info', 房间号, 游戏者, 旁观者, 裁判序号] # 房间信息
['room_list', [id, len(players), len(observers), state]] # 获取房间列表
['room_state', 状态] # 当前房间状态
['wcard_get', id, text] # 发送一张白卡，单人
['bcard_get', id, text] # 发送一张黑卡，广播
['wcard_sent', 用户序号，白卡序号] # 表示服务器已经收到打出的白卡，广播
['jcard_add', text] # 添加一张裁判卡，广播
['judge_set', 序号] # 设置裁判
['bcard_set', text] # 设置黑卡文本
'''
def my_index(lst, v):
    return lst.index(v) if v in lst else -1

class ROOM_STATE:
    LACK = 1 # 缺人
    PREPARE = 2 # 达到最低开房要求，房主可以开始
    CARD = 3 # 出牌阶段
    JUDGE = 4 # 裁判阶段

def remove_n(txt):
    return txt.replace('\n', '')

class CardPoll(object):

    wcards = {
        #fn : {name:'xxx', cards:[]}
    }

    bcards = {
        #fn : {name:'xxx', cards:[]}
    }

    def __init__(self):
        # 白卡名单
        self.wcgroup = []
        # 黑卡名单
        self.bcgroup = []
        # 加载默认两个卡组
        self.load_white('cards/white/默认白卡.txt')
        self.load_black('cards/black/默认黑卡.txt')
        
    def load_white(self, fn):
        if not fn in self.wcards:
            f = open(fn, 'r')
            cid = int(f.readline())
            cname = remove_n(f.readline())
            self.wcards[fn] = {
                'cid': cid,
                'name': cname,
                'cards': map(remove_n, f.readlines())
            }
            f.close()
        self.wcgroup.append(fn)

    def load_black(self, fn):
        if not fn in self.bcards:
            f = open(fn, 'r')
            cid = int(f.readline())
            cname = remove_n(f.readline())
            self.bcards[fn] = {
                'cid': cid,
                'name': cname,
                'cards': map(remove_n, f.readlines())
            }
            f.close()
        self.bcgroup.append(fn)

    def random_white(self):
        return [0,0,random.choice(self.wcards[self.wcgroup[0]]['cards'])]

    def random_black(self):
        return [0,0,random.choice(self.bcards[self.bcgroup[0]]['cards'])]

def delay(t, func):
    ioloop.IOLoop.instance().add_timeout(time.time() + t, func)

class Room(object):

    def __init__(self, rid):
        self.rid = rid
        self.state = ROOM_STATE.LACK
        self.visitors = []
        self.players = []
        self.observers = []

        # 下一回合加入players
        self._players = []

        # 卡池
        self.card_poll = CardPoll()

        self.init()

    def init(self):
        # 当前黑卡
        self.black_card = None
        # 手牌信息
        self.cards = {
            #uid : [白卡列表]
        }
        # 回合裁判
        self.judge = None
        # 已经出牌的玩家
        self.splayers = []
        # 被打出的牌
        self.jcards = []


    def state_check(self, sock, new_state=None):
        changed = False
        ins_lst = []

        if self.state == ROOM_STATE.LACK:
            # 人数 >= 3 切换至准备状态
            if len(self.players) + len(self._players) >= 3:
                self.state = ROOM_STATE.PREPARE
                changed = True

        elif self.state in [ROOM_STATE.PREPARE, ROOM_STATE.JUDGE]:
            # 参战人数过少，回到缺人状态
            if len(self.players) + len(self._players) < 3:
                self.init()
                self.state = ROOM_STATE.LACK
                changed = True

            # 发牌、出题、进入出牌阶段
            elif new_state == ROOM_STATE.CARD:
               # 处理上一回合数据
                self.splayers = []
                self.jcards = []
                # 处理玩家
                self.players += self._players
                self._players = []
                # 随机黑卡
                self.state = new_state
                self.black_card = self.card_poll.random_black()

                # 选择裁判
                while True:
                    judge = random.choice(self.players)
                    if judge != self.judge:
                        self.judge = judge
                        break

                ins_lst = [
                    ['bcard_set', self.black_card[2].replace('%b', '________'), self.black_card[2].count('%b')],
                    ['judge_set', my_index(self.players, self.judge)],
                ]

                # 随机白卡
                for i in self.players:
                    if not i.uid in self.cards:
                        self.cards[i.uid] = []

                    # 不足4张补到4张
                    if len(self.cards[i.uid]) < 4:
                        lst = []
                        for _ in range(4):
                            winfo = self.card_poll.random_white()
                            self.cards[i.uid].append(winfo)
                            lst.append(['wcard_get', winfo])
                        sock.broadcast([i], json.dumps(lst))

                    # 不然只给一个，最多8张
                    elif len(self.cards[i.uid]) < 8:
                        winfo = self.card_poll.random_white()
                        self.cards[i.uid].append(winfo)

                        sock.broadcast([i], json.dumps([
                            ['wcard_get', winfo],
                        ]))
    
                changed = True

        elif self.state == ROOM_STATE.CARD:
            # 参战人数过少，回到缺人状态
            if len(self.players) + len(self._players) < 3:
                self.state = ROOM_STATE.LACK
                self.init()
                changed = True
            # 试图切换至裁判状态
            elif new_state == ROOM_STATE.JUDGE:
                if len(self.splayers) == len(self.players) - 1:
                    self.state = ROOM_STATE.JUDGE
                    changed = True

        if changed:
            self.broadcast(sock, json.dumps([
               ['room_state', self.state],
            ] + ins_lst))

    def wcard_send(self, sock, lst):
        # 必须在出牌阶段
        if self.state != ROOM_STATE.CARD:
            return
        # 裁判不能出牌
        if sock == self.judge:
            return
        # 不能已经出牌
        if sock in self.splayers:
            return
        # 这个牌必须存在
        for i in lst:
            if int(i) >= len(self.cards[sock.uid]):
                return
        # 出牌
        self.splayers.append(sock)
        jc = []
        for i in lst:
            jc.append(self.cards[sock.uid].pop(i))
        self.jcards.append(jc)

        self.broadcast(sock, json.dumps([
           ['wcard_sent', self.players.index(sock), sorted(lst, reverse=True)],
           ['jcard_add', '&nbsp;&nbsp;'.join([c[2] for c in jc])],
        ]))

        self.state_check(sock, ROOM_STATE.JUDGE)

    def game_start(self, sock):
        self.state_check(sock, ROOM_STATE.CARD)

    def get_info(self):
        return [self.rid, len(self.players), len(self.observers), self.state]

    def wcard_choose(self, sock, index):
        """ 裁判：选择一张卡牌作为答案 """
        if self.state == ROOM_STATE.JUDGE and sock == self.judge:
            txt = self.black_card[2]
            for i in self.jcards[index]:
                txt = txt.replace('%b', '<u> %s </u>' % i[2], 1)
            self.broadcast(sock, json.dumps([
                ['wcard_choose', index, self.players.index(self.splayers[index])],
                ['bcard_set', txt],
            ]))
            delay(1, lambda:self.state_check(sock, ROOM_STATE.CARD))

    def room_info(self, sock):
        """ 广播房间用户信息 """
        sock.broadcast(self.visitors, json.dumps([
            ['room_info', self.rid,
                [[x.uid,x.uname] for x in self.players] + [[x.uid,x.uname] for x in self._players],
                [[x.uid,x.uname] for x in self.observers],
                -1 if self.state < ROOM_STATE.CARD else my_index(self.players, self.judge),
                [x.uid for x in self.splayers] # 已经选择答案的玩家
            ],
        ]))

    def join(self, sock, user):
        if user in self.visitors:
            return

        self.visitors.append(user)
        self.observers.append(user)
        self.room_info(sock)

        sock.broadcast(self.visitors, json.dumps([
            ['say', u"<b>[系统]</b> %s 加入游戏。" % user.uname],
        ]))

        self.state_check(sock)

    def user_cards_remove(self, user):
        """ 清除卡牌信息，更换裁判，抽除手牌 """
        if user.uid in self.cards:
            self.cards.pop(user.uid)
            # 如果退出的人已经出牌了，抽掉这张牌
            if user in self.splayers:
                i = self.splayers.index(user)
                self.splayers.pop(i)
                self.jcards.pop(i)
                self.broadcast(sock, json.dumps([
                    ['jcard_remove', i],
                ]))

        # 如果退出的人是裁判
        if self.judge == user:
            lst = []
            for i in self.players:
                if not i in self.splayers:
                    lst.append(i)

            # 如果可以，重新选择一个未出牌的人为裁判
            if lst:
                self.judge = random.choice(lst)
            # 不行的话，从所有玩家中挑选
            else:
                self.judge = random.choice(self.players)

    def leave(self, sock, user):
        inv = -1

        if user in self.players:
            self.players.remove(user)

        if user in self._players:
            self._players.remove(user)

        if user in self.observers:
            self.observers.remove(user)

        if user in self.visitors:
            inv = self.visitors.index(user)
            self.visitors.remove(user)

        if inv == -1:
            return

        # 移除信息
        self.user_cards_remove(user)

        # 广播
        if self.rid == 0:
            txt = u"<b>[系统]</b> %s 离开大厅。" % user.uname
        else:
            txt = u"<b>[系统]</b> %s 离开房间。" % user.uname

        self.broadcast(sock, json.dumps([
           ['room_leave', user.uid, user.uname],
           ['judge_set', my_index(self.players, self.judge)],
           ['say', txt],
        ]))

        self.state_check(sock, ROOM_STATE.JUDGE)

    def room_player(self, sock, role):
        # 改变身份：玩家/观察者
        if role == 1:
            if sock in self.observers:
                self.observers.remove(sock)
                if self.state <= ROOM_STATE.PREPARE:
                    self.players.append(sock)
                else:
                    self._players.append(sock)
        else:
            if sock in self._players:
                self._players.remove(sock)
                self.observers.append(sock)
            elif sock in self.players:
                self.players.remove(sock)
                self.observers.append(sock)

                # 清除卡牌信息，更换裁判，抽除手牌
                self.user_cards_remove(sock)
        
                # 刷新裁判
                if self.state >= ROOM_STATE.CARD:
                    sock.broadcast([sock], json.dumps([
                        ['judge_set', self.players.index(self.judge) if self.state >= ROOM_STATE.CARD else -1],
                    ]))
        
        self.state_check(sock, ROOM_STATE.JUDGE)
        self.room_info(sock)

    def broadcast(self, sock, txt):
        sock.broadcast(self.visitors, txt)


class SockAPIConnection(SockJSConnection):
    visitors = set()
    rooms = {0:Room(0)}

    def on_open(self, info):
        self.uname = ''
        self.room = -1
        self.visitors.add(self)
        self.uid = len(self.visitors)

    def room_add(self):
        room_id = 0
        for k, v in self.rooms.items():
            if int(k) > room_id:
                room_id = int(k)
        room_id += 1

        r = Room(room_id)
        self.rooms[room_id] = r

        self.rooms[0].broadcast(self, json.dumps([['room_add', r.get_info()],]))
        self.room_join(room_id)
        return r


    def room_join(self, rid):
        if self.room != -1:
            r = self.rooms[self.room]
            r.leave(self, self)
            if len(r.visitors) == 0 and self.room != 0:
                self.rooms.pop(self.room)
                self.room_list(True)
            self.room = -1

        self.rooms[rid].join(self, self)
        self.room = rid

    def room_list(self, broadcast=False):
        room_id = 0
        for k, v in self.rooms.items():
            if int(k) > room_id:
                room_id = int(k)
        room_id += 1

        ret = []
        for i in range(1, room_id):
            if i in self.rooms.keys():
                ret.append(self.rooms[i].get_info())

        to = self.rooms[0].visitors if broadcast else [self]
        self.broadcast(to, json.dumps([
            ['room_list', ret],
        ]))

    def say(self, txt):
        self.rooms[self.room].broadcast(self, json.dumps([['say', u"%s：%s" % (self.uname, txt)],]))

    def on_message(self, msg):
        info = json.loads(msg)
        for i in info:
            key = i[0]
            if key == 'set_uname':
                self.uname = i[1]
                self.broadcast([self], json.dumps([['user_info', self.uid]]))
            elif key == 'room_add':
                self.room_add()
            elif key == 'room_join':
                self.room_join(int(i[1]))
            elif key == 'say':
                self.say(i[1])
            elif key == 'room_list':
                self.room_list()
            elif key == 'room_player':
                self.rooms[self.room].room_player(self, int(i[1]))
            elif key == 'game_start':
                self.rooms[self.room].game_start(self)
            elif key == 'wcard_send':
                self.rooms[self.room].wcard_send(self, i[1])
            elif key == 'wcard_choose':
                self.rooms[self.room].wcard_choose(self, int(i[1]))

    def on_close(self):
        r = self.rooms[self.room]
        r.leave(self, self)
        if len(r.visitors) == 0 and self.room != 0:
            self.rooms.pop(self.room)
            self.room_list(True)
        self.visitors.remove(self)

